<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function Vue({ data }) {
            //在Vue内部把数据放在了vm._data上
            this._data = data;

            //1.数据代理:遍历_data的数据 在vm上放一份
            for (let key in this._data) {
                //给已经存在的实例对象vm 扩展一些属性,并且设置存取器属性,需要使用Object.defineProperty
                Object.defineProperty(this, key, {
                    get: function proxyGetter() {
                        return this._data[key];
                    },
                    set: function proxySetter(val) {
                        this._data[key] = val;
                    },
                });
            }

            //调用observe的方法,判断当前数据是否需要被数据劫持,如果需要进行数据劫持,则我们需要去实例化一个Observer
            observe(this._data);
        }
        //  封装一个方法,判断当前的数据是否需要完成数据劫持
        function observe(data) {
            if (typeof data !== "object" || data === null) return
            // 实例化一个发布者
            new Observer(data)
        }
        class Observer {
            constructor(data) {
                this.data = data
                // 判断当前需要被劫持的值是数组还是对象,然后分发到不同的方法中
                if (Array.isArray(this.data)) {
                    this.observeArray(this.data)
                } else {
                    this.walk(this.data)
                }
            }
            // 处理数组格式的劫持方法
            observeArray(data) {
                data.forEach((item) => {
                    observe(item)
                })
            }
            // 处理对象格式的劫持方法
            walk(data) {
                for (let key in data) {
                    defineReactive(data, key)
                }
            }
        }

        // 数据劫持的功能核心
        function defineReactive(data, key) {
            let value = data[key]
            // 调用observe方法,判断当前value值是不是一个对象或者数据来进行深层次的劫持
            observe(value);
            Object.defineProperty(data, key, {
                get: function reactiveGetter() {
                    return value
                },
                set: function reactiveSetter(newVal) {
                    // 判断如果新值和旧值一样,则不做任何操作
                    if (newVal === value) return
                    // 否则给value赋值,当再次取值的时候,得到就是最新的value
                    value = newVal
                    // 重新获取数据
                    console.log("修改值被劫持，模板重新获取值，然后进行更新渲染")
                }
            })
        }

        //创建vue实例
        const vm = new Vue({
            data: {
                count: 123,
                user: {
                    name: "xiaowang",
                    age: 18,
                },
                course: ["html", "js", "css"],
                person: [
                    { id: "001", name: "xiaowang" },
                    { id: "002", name: "小李" },
                ],
            },
        });
    </script>
</body>

</html>